﻿using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Microsoft.WindowsAzure.Storage.Table.Queryable;

namespace CloudStorageLight.Core
{
    /// <summary>
    /// ログサービス
    /// </summary>
    public class LogService
    {
        private string connectionString { get; set; }

        public static LogService DefaultLogService
        {
            get { return _DefaultLogService; }
        }
        private static LogService _DefaultLogService = new LogService(SystemSettings.Instance.BlobStorage);

        public LogService(string connectionString)
        {
            this.connectionString = connectionString;
        }
        /// <summary>
        /// ログの書き込み
        /// </summary>
        /// <param name="user"></param>
        /// <param name="action"></param>
        /// <param name="path"></param>
        /// <param name="Additional"></param>
        /// <returns></returns>
        public virtual void EntryLog(string userName, string account, string action, string path, string Additional, HttpApplicationState app = null)
        {
            var newLog = new AccessLog(account, DateTime.UtcNow) { UserName = userName.ToLower(), Action = action, Path = path, Additional = Additional };
            if (app == null)
            {
                if (HttpContext.Current != null)
                {
                    app = HttpContext.Current.Application;
                }
                else
                {
                    return; //TEST MODE
                }

            }

            lock (lockObject)
            {
                var list = (List<AccessLog>)app["BlobServiceLog"];
                if (list == null)
                {
                    list = new List<AccessLog>();
                    app["BlobServiceLog"] = list;
                }
                list.Add(newLog);
                if (list.Count > 10000)
                {
                    list.RemoveAt(0);
                }
            }
            Trace.WriteLine(newLog.ToString());
        }

        private static object lockObject = new object();

        public virtual void FlushLog(HttpApplicationState app = null)
        {
            if (app == null)
            {
                if (HttpContext.Current != null)
                {
                    app = HttpContext.Current.Application;
                }
                else
                {
                    return; //TEST MODE
                }
            }
            var storageAccount = CloudStorageAccount.Parse(connectionString);
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
            var table = tableClient.GetTableReference("AccessLog");
            table.CreateIfNotExists();
            List<AccessLog> list = null; 
            lock (lockObject)
            {
                if (app["BlobServiceLog"] != null)
                {
                    list = ((List<AccessLog>)app["BlobServiceLog"]).OrderBy(x => x.PartitionKey).ToList();
                    ((List<AccessLog>)app["BlobServiceLog"]).Clear();

                }
            }
            while (true)
            {
                if (list == null || list.Count == 0) return;
                TableBatchOperation batchOperation = new TableBatchOperation();

                int cnt = 0;
                string prekey = null;
                foreach (var item in list.Take(100))
                {
                    if (prekey != null && item.PartitionKey != prekey) break;
                    batchOperation.Insert(item);
                    cnt++;
                    prekey = item.PartitionKey;
                }
                table.ExecuteBatch(batchOperation);
                for (int i = 0; i < cnt; i++)
                {
                    list.RemoveAt(0);
                }
            }
        }

        #pragma warning disable 1998
        public virtual async Task<List<AccessLog>> GetLog(string account, DateTime? from, DateTime? to, string userName, string[] targetAction = null, string path = null, int maxRecords = 0)
        {
            account = account ?? "";
            var storageAccount = CloudStorageAccount.Parse(connectionString);
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
            TableBatchOperation batchOperation = new TableBatchOperation();
            var table = tableClient.GetTableReference("AccessLog");

            string rangeFrom;
            if (from.HasValue)
            {
                rangeFrom = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, account + "_" + from.Value.ToString("yyyyMMdd"));
            }
            else
            {
                rangeFrom = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, account + "_00000000");
            }
            string rangeTo;
            if (to.HasValue)
            {
                if (to.Value.Date == to.Value)
                {
                    rangeTo = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThan, account + "_" + to.Value.AddDays(1).ToString("yyyyMMdd"));
                }
                else
                {
                    rangeTo = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThanOrEqual, account + "_" + to.Value.ToString("yyyyMMdd"));
                }
            }
            else
            {
                rangeTo = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThanOrEqual, account + "_99999999" );
            }
            var filter = TableQuery.CombineFilters(rangeFrom, "and", rangeTo);

            if (!string.IsNullOrEmpty(userName))
            {
                filter = TableQuery.CombineFilters(filter, "and", 
                    TableQuery.GenerateFilterCondition("UserName", QueryComparisons.Equal, userName.ToLower()));

            }

            if (targetAction != null && targetAction.Count() > 0)
            {
                var actionFilters = new List<string>();
                foreach(var action in targetAction)
                {
                    actionFilters.Add(TableQuery.GenerateFilterCondition("Action", QueryComparisons.Equal, action));
                }
                filter = TableQuery.CombineFilters(filter, "and", string.Join(" or ", actionFilters));
            }

            if (!string.IsNullOrEmpty(path))
            {
                if (path.EndsWith("/"))
                {
                    var pathRangeFrom =TableQuery.GenerateFilterCondition("Path", QueryComparisons.GreaterThanOrEqual, path);
                    var pathRangeTo = TableQuery.GenerateFilterCondition("Path", QueryComparisons.LessThan, path.Substring(0, path.Length-1) + "0");
                    filter = TableQuery.CombineFilters(filter, "and", 
                        TableQuery.CombineFilters(pathRangeFrom, "and", pathRangeTo));
                }
                else
                {
                    filter = TableQuery.CombineFilters(filter, "and",
                        TableQuery.GenerateFilterCondition("Path", QueryComparisons.Equal, path));
                }

            }

            var query = new TableQuery<AccessLog>();
            var list = new List<AccessLog>();
            if (maxRecords == 0 || !from.HasValue)
            {
                list = table.ExecuteQuery(query.Where(filter)).ToList();
            }
            else
            {
                var workTo = to ?? DateTime.UtcNow.AddHours(24);
                while (maxRecords > list.Count)
                {
                    var nextFrom = workTo.AddHours(-12); //時間の範囲指定
                    if (nextFrom < from.Value) nextFrom = from.Value;　

                    var rangeToPartial = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThanOrEqual, account + "_" + workTo.ToString("yyyyMMdd"));
                    var rangeToRow = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, workTo.ToString("yyyyMMdd HH:mm:ss:fff"));
                    var rangeFromPartial = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, account + "_" + nextFrom.ToString("yyyyMMdd"));
                    var rangeFromRow = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, nextFrom.ToString("yyyyMMdd HH:mm:ss:fff"));
                    var rangeFilter = TableQuery.CombineFilters(TableQuery.CombineFilters(rangeToPartial, "and", rangeToRow), "and", TableQuery.CombineFilters(rangeFromPartial, "and", rangeFromRow));
                    var workList = table.ExecuteQuery(query.Where(TableQuery.CombineFilters(filter, "and", rangeFilter)));

                    list.AddRange(workList);
                    if (nextFrom == from.Value) break;
                    workTo = nextFrom.AddMilliseconds(-1);　//ミリセカンド単位
                }

            }
            if (from.HasValue) list = list.Where(x => x.Date >= from.Value).ToList();
            if (to.HasValue)
            {
                if (to.Value.Date == to.Value)
                {
                    list = list.Where(x => x.Date < to.Value.AddDays(1)).ToList();
                }
                else
                {
                    list = list.Where(x => x.Date <= to.Value).ToList();
                }
            }
            if (!string.IsNullOrEmpty(path))
            {
                list = list.Where(x => x.Path.StartsWith(path)).ToList();
            }
            if (maxRecords > 0) list = list.OrderByDescending(x=>x.Date).Take(maxRecords).ToList();
            return list.ToList();
        }
#pragma warning restore 1998

        public virtual List<FileAccessLog> GetFileLog(string account, string path, int maxRecords = 0)
        {
            account = account ?? "";
            var storageAccount = CloudStorageAccount.Parse(connectionString);
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
            var table = tableClient.GetTableReference("FileAccessLog");

            var fileAccessLog = new FileAccessLog(account, path, null);

            var partKey = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, fileAccessLog.PartitionKey);
            var pathFilter = TableQuery.GenerateFilterCondition("Path", QueryComparisons.Equal, path);
            var query = new TableQuery<FileAccessLog>();
            var list = table.ExecuteQuery(query.Where(TableQuery.CombineFilters(partKey, "and", pathFilter))).ToList();
            if (maxRecords > 0) return list.OrderByDescending(x=>x.Date).Take(maxRecords).ToList();
            return list.OrderByDescending(x => x.Date).ToList();
        }
#pragma warning restore 1998


        public virtual async Task DeleteLog(string account, DateTime to)
        {
            account = account ?? "";
            var storageAccount = CloudStorageAccount.Parse(connectionString);
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
            var table = tableClient.GetTableReference("AccessLog");

            var rangeFrom = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, account + "_00000000");
            var rangeTo = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThanOrEqual, account + "_" + to.ToString("yyyyMMdd"));
            var filter = TableQuery.CombineFilters(rangeFrom, "and", rangeTo);

            TableQuerySegment<AccessLog> segment = null;

            while (segment == null || segment.ContinuationToken != null)
            {
                var query = new TableQuery<AccessLog>();
                segment =  await table.ExecuteQuerySegmentedAsync<AccessLog>(query.Where(filter), segment == null ? null : segment.ContinuationToken);
                var list = segment.Results.Where(x=>x.AccountName == account && x.Date <= to).OrderBy(x=>x.PartitionKey).ToList();

                while (true)
                {
                    if (list == null || list.Count == 0) break;
                    TableBatchOperation batchOperation = new TableBatchOperation();

                    int cnt = 0;
                    string prekey = null;
                    foreach (var item in list.Take(100))
                    {
                        if (prekey != null && item.PartitionKey != prekey) break;
                        batchOperation.Delete(item);
                        cnt++;
                        prekey = item.PartitionKey;
                    }
                    table.ExecuteBatch(batchOperation);
                    for (int i = 0; i < cnt; i++)
                    {
                        list.RemoveAt(0);
                    }
                }

            }
        }

        public virtual async Task EnsureFileAccessLog(string account)
        {
            account = account ?? "";
            var storageAccount = CloudStorageAccount.Parse(connectionString);
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
            var logTable = tableClient.GetTableReference("AccessLog");
            var fileTable = tableClient.GetTableReference("FileAccessLog");
            fileTable.CreateIfNotExists();

            var filePart = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, account);
            var fileRow = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "");
            var fileFilter = TableQuery.CombineFilters(filePart, "and", fileRow);
            var fileQuery = new TableQuery<MarkFileAccessLog>();
            var targetAction = new string[] { BlobService.LogUpload, BlobService.LogDownload, BlobService.LogDelete, BlobService.LogRename, BlobService.LogMove, BlobService.LogDelete, BlobService.LogDeleteFolder, BlobService.LogCheckIn, BlobService.LogCheckOut, BlobService.LogCreate, };
            var actionFilters = new List<string>();
            foreach (var action in targetAction)
            {
                actionFilters.Add(TableQuery.GenerateFilterCondition("Action", QueryComparisons.Equal, action));
            }

            var lastUpdate = fileTable.ExecuteQuery(fileQuery.Where(fileFilter)).FirstOrDefault();

            var rangeFrom = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, account + "_00000000");
            var rangeTo= TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThanOrEqual, account + "_99999999");
            if (lastUpdate != null)
            {
                var partKey = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, lastUpdate.LastUpdatePartitionKey);
                var rowKey = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThan, lastUpdate.LastUpdateRowKey);
                rangeFrom = TableQuery.CombineFilters(partKey, "and", rowKey);
            }

            TableQuerySegment<AccessLog> segment = null;

            while (segment == null || segment.ContinuationToken != null)
            {
                var query = new TableQuery<AccessLog>();
                var queryFilter = TableQuery.CombineFilters(
                    TableQuery.CombineFilters(rangeFrom,"and",rangeTo),
                    "and", string.Join(" or ", actionFilters));
                segment = await logTable.ExecuteQuerySegmentedAsync<AccessLog>(query.Where(queryFilter), segment == null ? null : segment.ContinuationToken);
                var list = segment.Results.ToList();
                if (list == null || list.Count == 0) continue;

                //最も大きいRowKeyを記録
                var lastItem = list.Last();
                var markItem = new MarkFileAccessLog(account);
                markItem.LastUpdatePartitionKey = lastItem.PartitionKey;
                markItem.LastUpdateRowKey = lastItem.RowKey;

                var newList = new List<FileAccessLog>();
                foreach(var item in list)
                {
                    var newItem = new FileAccessLog(item.AccountName, item.Path, item.RowKey) { Action = item.Action, IPAddress = item.IPAddress, UserName = item.UserName };
                    newList.Add(newItem);
                }
                newList = newList.OrderBy(x => x.PartitionKey).ToList();


                while (true)
                {
                    if (newList == null || newList.Count == 0) break;
                    TableBatchOperation batchOperation = new TableBatchOperation();

                    int cnt = 0;
                    string prekey = null;
                    foreach (var newItem in newList.Take(100))
                    {
                        if (prekey != null && newItem.PartitionKey != prekey) break;
                        batchOperation.InsertOrReplace(newItem);
                        cnt++;
                        prekey = newItem.PartitionKey;
                    }
                    fileTable.ExecuteBatch(batchOperation);
                    for (int i = 0; i < cnt; i++)
                    {
                        newList.RemoveAt(0);
                    }
                }

                fileTable.Execute(TableOperation.InsertOrReplace(markItem));
            }
        }

    }
}
